/* Copyright (C) 2007-2008  klevgert  http://code.google.com/p/db4o-netbeans/

This file is part of the db4o-netbeans module suite.

db4o-netbeans is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

db4o-netbeans is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */

package com.db4o.nb.editor.nodes;

import com.db4o.ObjectContainer;
import com.db4o.ext.ExtObjectContainer;
import com.db4o.ext.SystemInfo;
import com.db4o.nb.api.Db4oDatabase;
import com.db4o.nb.api.Db4oImporter;
import com.db4o.nb.api.impl.XmlImporterImpl;
import com.db4o.nb.editor.dialogs.AddObjectsDialog;
import com.db4o.nb.editor.dialogs.SelectTypeDialog;
import com.db4o.nb.util.ClassLoaderUtil;
import com.db4o.nb.util.DialogUtil;
import com.db4o.nb.util.ExtensionFileFilter;
import com.db4o.nb.util.PreferencesUtil;
import com.db4o.reflect.ReflectClass;
import java.awt.event.ActionEvent;
import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.filechooser.FileFilter;
import org.openide.ErrorManager;
import org.openide.actions.PropertiesAction;
import org.openide.loaders.DataObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Node.Property;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.Lookups;

/**
 * Db4o Data Editor node that represents the database root.
 *
 * @author klevgert
 */
public class DatabaseNode extends AbstractNode {
  
  static final String ICON_PATH = "com/db4o/nb/editor/resources/beantree.gif";
  
  /** Holds the opened db4o database */
  private Db4oDatabase database;

  /** Creates a new instance of DatabaseNode */
//  public DatabaseNode(ObjectContainer oc) {
  public DatabaseNode(Db4oDatabase database, DataObject dataObj) {
    super (new DatabaseNodeChildren(database), Lookups.fixed( new Object[] { database, dataObj }));
    
    this.database = database;
    
    String displayName = NbBundle.getMessage(this.getClass(), "DatabaseContent" );
    this.setDisplayName (displayName);    
    this.setIconBaseWithExtension(ICON_PATH);
  }

  /**
   * Gets the opened db4o database.
   * @return db4o object container 
   */
  public ObjectContainer getObjectContainer() {
    return this.database.getObjectContainer();
  }

  /**
   * Gets all action that can be performed on this node.
   * @return Array of actions.
   */
  public Action[] getActions(boolean b) {
    
    Action[] actions = new Action[] {
      new AddObjectAction(),
      new AddObjectsAction(),
      null,
      new CommitChangesAction(),
      new RollbackChangesAction(),
      null,
//      new ImportDataAction(),
//      null,
      SystemAction.get(PropertiesAction.class)      
    };
    
    return actions;
  }  
  
  // <editor-fold defaultstate="collapsed" desc=" Properties ">                          
  
  /**
   * Creates a properties sheet for this node. 
   * @return properties sheet
   */
  protected Sheet createSheet() {
    
    Sheet sheet = Sheet.createDefault();
    Sheet.Set set = sheet.createPropertiesSet();
    Sheet.Set set2 = sheet.createPropertiesSet();
    
    String sysinfo = NbBundle.getMessage(this.getClass(), "SysInfo" );    
    set2.setDisplayName(sysinfo);
    set2.setName("sysInfo");
    try {
        Property fileProp = createFileProperty();
        set.put (fileProp);
        Property versionProp = createVersionProperty();
        set.put (versionProp);
        Property stateProp = createStateProperty();
        set.put (stateProp);
        Property timeProp = createCreationTimeProperty();
        set.put (timeProp);
        Property[] systemInfoProps = createSystemInfoProperties();
        for(int i=0; i<systemInfoProps.length; i++) {
          set2.put(systemInfoProps[i]);
        }
        
    } catch (Exception ex) {
        ErrorManager.getDefault().notify (ex);
    }
    sheet.put(set);
    return sheet;
  }

  /**
   * creates the db4o database file property
   */
  private Property createFileProperty() {
    
    String displayName = NbBundle.getMessage(this.getClass(), "File" );

    Property fileProp = new PropertySupport.ReadOnly("file", String.class, 
            displayName, null) {
      public Object getValue() {
        return DatabaseNode.this.getObjectContainer().toString();
      }
    };
    fileProp.setName("file");
    return fileProp;
  }

  /**
   * creates the db4o database version property
   */
  private Property createVersionProperty() {
    
    String displayName = NbBundle.getMessage(this.getClass(), "Version" );

    Property verProp = new PropertySupport.ReadOnly("version", Long.class, 
            displayName, null) {
      public Object getValue() {
        Long val = new Long(DatabaseNode.this.getObjectContainer().ext().version());
        return val;
      }
    };
    verProp.setName("version");
    return verProp;
  }

  /**
   * creates the db4o database version property
   */
  private Property createCreationTimeProperty() {
    
    String displayName = NbBundle.getMessage(this.getClass(), "CreationTime" );

    Property verProp = new PropertySupport.ReadOnly("creationTime", String.class, 
            displayName, null) {
      public Object getValue() {
        long time = DatabaseNode.this.getObjectContainer().ext().identity().getCreationTime();
        DateFormat df = new SimpleDateFormat();
        return df.format(new Date(time));
      }
    };
    verProp.setName("creationTime");
    return verProp;
  }
  
  /**
   * creates the db4o database state property
   */
  private Property createStateProperty() {
    
    String displayName = NbBundle.getMessage(this.getClass(), "State" );
    Property stateProp = new PropertySupport.ReadOnly("state", String.class, 
            displayName, null) {
      public Object getValue() {
        return (DatabaseNode.this.getObjectContainer().ext().isClosed())? "Closed" : "Open";
      }
    };
    stateProp.setName("state");
    return stateProp;
  }
  
  /**
   * creates the db4o database file property
   */
  private Property[] createSystemInfoProperties() {
    
    Property[] props;
    
    try {
      
      final SystemInfo systemInfo = this.getObjectContainer().ext().systemInfo();

      String displayName = NbBundle.getMessage(this.getClass(), "TotalSize" );

      Property totalSizeProp = new PropertySupport.ReadOnly("totalSize", Long.class, 
              displayName, null) {
        public Object getValue() {
          return new Long(systemInfo.totalSize());
        }
      };
      totalSizeProp.setName("totalSize");
      
      displayName = NbBundle.getMessage(this.getClass(), "FreeSpaceSize" );
      Property freespaceProp = new PropertySupport.ReadOnly("freespaceSize", Long.class, 
              displayName, null) {
        public Object getValue() {
          return new Long(systemInfo.freespaceSize());
        }
      };
      freespaceProp.setName("freespaceSize");

      displayName = NbBundle.getMessage(this.getClass(), "FreeSpaceEntryCount" );
      Property freespaceEntriesProp = new PropertySupport.ReadOnly("freespaceEntryCount", Long.class, 
              displayName, null) {
        public Object getValue() {
          return new Long(systemInfo.freespaceEntryCount());
        }
      };
      freespaceEntriesProp.setName("freespaceEntryCount");
      props = new Property[] { totalSizeProp, freespaceProp, freespaceEntriesProp };

    } catch( com.db4o.foundation.NotImplementedException e ) {
      
      props = new Property[] {};
    }
    
    return props;
  }
  
  // </editor-fold>                            
  
  // <editor-fold defaultstate="collapsed" desc=" Actions ">                          

  /**
   * Action to commit all changes made so far explicitly. 
   */
  private class CommitChangesAction extends AbstractAction {
    
    public CommitChangesAction() {
      String name = NbBundle.getMessage(this.getClass(), "CommitChanges");
      this.putValue(NAME, name);
    }
    
    public void actionPerformed(ActionEvent e) {
      DatabaseNode.this.getObjectContainer().commit();
    }
  }
  
  /**
   * Action to commit all changes made so far explicitly. 
   */
  private class RollbackChangesAction extends AbstractAction {
    
    public RollbackChangesAction() {
      String name = NbBundle.getMessage(this.getClass(), "RollbackChanges");
      this.putValue(NAME, name);
    }
    
    public void actionPerformed(ActionEvent e) {
      DatabaseNode.this.getObjectContainer().rollback();
    }
  }

  /**
   * Action to add objects to the database. 
   */
  private class AddObjectAction extends AbstractAction {
    
    public AddObjectAction() {
      String name = NbBundle.getMessage(this.getClass(), "AddObject");
      this.putValue(NAME,name);
    }
    
    public void actionPerformed(ActionEvent e) {
      
      SelectTypeDialog std = new SelectTypeDialog(new JFrame(),true);
      std.setObjectContainer(DatabaseNode.this.getObjectContainer());
      std.setVisible(true);

      if( std.getReturnStatus() == SelectTypeDialog.RET_OK) {

        String type = std.getSelectedClass();
        if( type.length() > 0 ) {
          
          try {

            Class clazz = ClassLoaderUtil.getClassLoader().loadClass(type);
            Object inst = clazz.newInstance();
            DatabaseNode.this.getObjectContainer().set(inst);

            DatabaseNode.this.setChildren( new DatabaseNodeChildren(database) );

          } catch (Exception ex) {
            
            String errmsg = NbBundle.getMessage(this.getClass(), "CannotCreateObject", ((ex!=null)? ex.toString(): "") );
            DialogUtil.showErrorMessage(errmsg);
          }
        }
      }
      
 //     DialogDescriptor dd = new DialogDescriptor( std , title);
      
    }
  }

  /**
   * Action to add objects to the database. 
   */
  private class AddObjectsAction extends AbstractAction {
    
    public AddObjectsAction() {
      String name = NbBundle.getMessage(this.getClass(), "AddObjects");
      this.putValue(NAME,name);
    }
    
    public void actionPerformed(ActionEvent e) {
      
      AddObjectsDialog std = new AddObjectsDialog(JFrame.getFrames()[0],true);
      std.setObjectContainer(DatabaseNode.this.getObjectContainer());
      std.setVisible(true);

      if( std.getReturnStatus() == SelectTypeDialog.RET_OK) {

        String type = std.getType();
        if( type.length() > 0 ) {
          
          try {

            Class clazz = ClassLoaderUtil.getClassLoader().loadClass(type);
            
            for(int i=0; i<std.getCount().intValue(); i++) {
              Object inst = clazz.newInstance();
              DatabaseNode.this.getObjectContainer().set(inst);
            }

            DatabaseNode.this.setChildren( new DatabaseNodeChildren(database) );

          } catch (Exception ex) {
            
            String errmsg = NbBundle.getMessage(this.getClass(), "CannotCreateObject", ((ex!=null)? ex.toString(): "") );
            DialogUtil.showErrorMessage(errmsg);
          }
        }
      }
      
 //     DialogDescriptor dd = new DialogDescriptor( std , title);
      
    }
  }

  /**
   * Action to commit all changes made so far explicitly. 
   */
  private class ImportDataAction extends AbstractAction {
    
    public ImportDataAction() {
      String name = NbBundle.getMessage(this.getClass(), "ImportData");
      this.putValue(NAME,name);
    }
    
    public void actionPerformed(ActionEvent e) {
      
      JFileChooser fileChooser = new JFileChooser(".");
      fileChooser.setFileFilter(new ExtensionFileFilter("XML", new String[] { "XML" }));
      int status = fileChooser.showOpenDialog(null);
      if (status == JFileChooser.CANCEL_OPTION) {
        return;
      }
      File selectedFile = fileChooser.getSelectedFile();
      
      Db4oImporter importer = new XmlImporterImpl();
      importer.importData(DatabaseNode.this.getObjectContainer(), selectedFile.getAbsolutePath());
    }      
  }
  
  // </editor-fold>                          

  // <editor-fold defaultstate="collapsed" desc=" Children ">                          

  /**
   * Holder for the children nodes of the database node. 
   * @author klevgert
   */
  public static class DatabaseNodeChildren extends Children.Keys {

    protected Db4oDatabase database;

    /** Creates a new instance of DatabaseChildren */
    public DatabaseNodeChildren(Db4oDatabase database) {
      this.database = database;
    }

    /**
     * Creates a sub node for a child object.
     * @param obj    object instance to create the sub node for.
     */
    protected Node[] createNodes(Object obj) {
      ReflectClass rc = (ReflectClass) obj;
      AbstractNode result = new ClassNode(rc,this.database.getObjectContainer());
      return new Node[] { result };
    }  

    /**
     * cache the child objects. 
     */
    protected void addNotify() {

      try {
        
        if( this.database == null ) {
          ErrorManager.getDefault().log(ErrorManager.ERROR, "No database");
          return;
        }

        // get the classes stored in db4o
        ObjectContainer oc = this.database.getObjectContainer();
        ExtObjectContainer eoc = oc.ext();
        ReflectClass[] classes = eoc.knownClasses();

        ArrayList keys = new ArrayList();
        // Iterate the classes and create a child node for each
        for (int i = 0; i < classes.length; i++) {

          ReflectClass reflectClass = classes[i];
          System.out.println(reflectClass.getName());

          // Ignore suppressed classes (see preferences)
          if( PreferencesUtil.isSuppressedPackage(reflectClass)) {
            continue;
          }

          // Setup a class node 
          keys.add(reflectClass);
        }
        setKeys (keys.toArray());
      
      } catch( Exception ex ) {
        
        ErrorManager.getDefault().log(ErrorManager.EXCEPTION, ex.getMessage());

        String errmsg = NbBundle.getMessage(this.getClass(), "CannotAccessContent", ((ex!=null)? ex.toString(): "") );
        DialogUtil.showErrorMessage(errmsg);
        
      }
    }  

  }
  
  // </editor-fold>                          

  
}
